'''
A Ping-pong server and client

@author: oraby
'''
import sys
import time

from twisted.internet import reactor
from twisted.internet.protocol import ClientFactory, ClientCreator, ServerFactory
from twisted.protocols.basic import LineOnlyReceiver


class Data:
    trackerAddr = None
    defaultPingLimit = 500
    pingLimit = None 
    myServerPort = None
    
class PingServer(LineOnlyReceiver):
    def lineReceived(self, line):
        self._count += 1
        line = "Pong " + str(self._count)
        #print line
        self.sendLine(line)
        if self._count == Data.pingLimit:
            #print "Loosing Connection."
            self.transport.loseConnection()
    
    def connectionMade(self):
        self._count = 0
        
class  PingServerFactory(ServerFactory):
    protocol = PingServer


class pingClient(LineOnlyReceiver):

    def lineReceived(self, line):
        self._count += 1
        line = "Ping " + str(self._count)
        #print line
        self.sendLine(line)
        if self._count == Data.pingLimit:
            self.transport.loseConnection()

    def connectionMade(self):
        self._startTime = time.time()
        self._count = 1
        line = "Ping " + str(self._count)
        self.sendLine(line)
        
    def connectionLost(self, reason):
        #print reason
        timeDiff = time.time() - self._startTime
        #print "Made", self._count, "ping @Time= " + repr(timeDiff)
        pass

    
class TrackRegister(LineOnlyReceiver):    
    def __init__(self):
        self._port = Data.myServerPort
        self._putRequested = False
    
    def connectionMade(self):
        self._registerSelf()

    def _registerSelf(self):
        line = "put: " + str(self._port)
        self._putRequested = True
        self.sendLine(line)

    def lineReceived(self, line):
        op, params = line.split(":",1)
        op = op.strip().upper()
        if op == "PUTREPLY":
            if self._putRequested:
                self._putRequested = False
                #reactor.callLater(0,getPeer)
                params = params.split()
                if len(params) == 2:
                    ip = params[0]
                    try:
                        port = int(params[1])
                    except ValueError:
                        print "Got invalid port from tracker"
                        self.transport.loseConnection()
                    else:
                        address = (ip,port)
                        #print "I'm registered & Got Peer: " + ip + ":"  + str(port)
                        reactor.callLater(0,gotPeer,address)
                else:
                    print "Got Wrong address format from tracker"
                    self.transport.loseConnection()
            else:
                print "Tracker Bluffing, terminating connection"
        self.transport.loseConnection()

    
def parseArgs():
    if len(sys.argv) < 2:
        print "Wrong Usage."
        usage = sys.argv[0] + " trackerAddress:port [pingLimit]"
        print "Usage: " + usage    
        sys.exit()
    trackerAddress = sys.argv[1]
    trackerAddress = trackerAddress.split(":")
    if len(trackerAddress) != 2:#IP,Port
        print "Invalid Tracker Address."
        print "Expected: IP:PORT \tGot:" + sys.argv[1]
        sys.exit(),
    ip, port = trackerAddress
    try:
        port = int(port)
    except ValueError:
        print "Invalid tracker port, An Integer required."
        print "Got: " + port
        sys.exit()
    else:
        Data.trackerAddr = (ip,port)
    if len(sys.argv) > 2:
        try:
            pingLimit = int(sys.argv[2])
        except ValueError:
            print "Invalid pingLimit, An Integer required."
            print "Got: " + sys.argv[2]
            sys.exit()
        else:
            Data.pingLimit = pingLimit
    else:
        Data.pingLimit = Data.defaultPingLimit


def gotPeer(address):
    '''Got a random peer from the tracker, lets connect to him'''
    def peerConnSucc(Connector):
        #print "Connection to Peer established"
        pass

    def peerConnFailed(reason):
        print "Connection to peer Failed"
        print "Reason: " + str(reason)
        #Try to get another peer
        registerWithTracker()
    #print "Got Address."
    #print "Address: " + str(address)
    pinger = ClientCreator(reactor, pingClient)
    d = pinger.connectTCP(address[0], address[1])
    d.addCallbacks(callback = peerConnSucc, errback = peerConnFailed)

def registerWithTracker():
    def trackerConnSucc(connector):
        #print "Connection to tracker succeeded"
        pass
    def trackerConnFailed(reason):
        print "Connection to tracker failed"
        reactor.stop()
    booting = ClientCreator(reactor, TrackRegister) 
    d = booting.connectTCP(Data.trackerAddr[0], Data.trackerAddr[1])
    d.addCallbacks(callback = trackerConnSucc, errback = trackerConnFailed)

def main():
    parseArgs()
    #Start listening
    twistedPort = reactor.listenTCP(0,PingServerFactory())
    serverPort = twistedPort._realPortNumber #I'm sorry to do this hack, but Twisted didn't help me.
    #Connect to Tracker and register my listening server and get a peer.
    Data.myServerPort = serverPort
    registerWithTracker()
    reactor.run()

    
if __name__ == '__main__':
    main()